﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace SEOBlog.Web.Helpers
{
    public interface INode
    {
        string Id { get; set; }

        string ParentId { get; set; }
    }

    public class TreeNode<T> where T : INode, new()
    {
        public T Data { get; set; }

        public ICollection<TreeNode<T>> ChildNodes { get; set; }

        public TreeNode<T> Parent { get; set; }
    }

    public class NaryTree<T> where T : INode, new()
    {
        private readonly IEnumerable<TreeNode<T>> _root;

        public NaryTree(IEnumerable<T> rootNodes)
        {
            _root = rootNodes.Select(x => new TreeNode<T> {Data = new T {Id = x.Id}});
        }

        private void Add(IEnumerable<TreeNode<T>> root, TreeNode<T> newNode)
        {
            foreach (var treeNode in root)
            {
                if (treeNode.Data.Id == newNode.Data.ParentId)
                {
                    if (treeNode.ChildNodes == null)
                    {
                        treeNode.ChildNodes = new List<TreeNode<T>>();
                    }

                    treeNode.ChildNodes.Add(newNode);
                    newNode.Parent = treeNode;
                }
                else
                {
                    if (treeNode.ChildNodes != null)
                    {
                        Add(treeNode.ChildNodes, newNode);
                    }
                }
            }
        }

        public void Add(T data)
        {
            var newNode = new TreeNode<T> { Data = data };
            Add(_root, newNode);
        }

        private void InOrderTraverse(IEnumerable<TreeNode<T>> root, Action<T> beforeVisitNode, Action<T> afterVisitNode, Action<T> beforeVisitRoot, Action<T> afterVisitRoot, Action<T> visitLeaf)
        {
            foreach (var treeNode in root)
            {
                if (treeNode.ChildNodes != null)
                {
                    if (treeNode.Parent == null)
                    {
                        beforeVisitRoot(treeNode.Data);
                        InOrderTraverse(treeNode.ChildNodes, beforeVisitNode, afterVisitNode, beforeVisitRoot, afterVisitRoot, visitLeaf);
                        afterVisitRoot(treeNode.Data);
                    }
                    else
                    {
                        beforeVisitNode(treeNode.Data);
                        InOrderTraverse(treeNode.ChildNodes, beforeVisitNode, afterVisitNode, beforeVisitRoot, afterVisitRoot, visitLeaf);
                        afterVisitNode(treeNode.Data);
                    }
                }
                else
                {
                    visitLeaf(treeNode.Data);
                }
            }
        }

        public void TraverseTree(Action<T> beforeVisitNode, Action<T> afterVisitNode, Action<T> beforeVisitRoot, Action<T> afterVisitRoot, Action<T> visitLeaf)
        {
            InOrderTraverse(_root, beforeVisitNode, afterVisitNode, beforeVisitRoot, afterVisitRoot, visitLeaf);
        }
    }
}